import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import re
import os
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
from collections import defaultdict, Counter


class BibTypeAnalyzer:
    def __init__(self, root):
        self.root = root
        self.root.title("BibTeX Type Analyzer")
        self.root.geometry("900x700")

        # Store the file paths and their analysis results
        self.selected_files = []
        self.file_analysis = {}
        self.file_contents = {}  # Store the full content of each file

        # Main frame
        main_frame = ttk.Frame(root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # File selection frame
        file_frame = ttk.LabelFrame(main_frame, text="File Selection", padding="10")
        file_frame.pack(fill=tk.X, pady=5)

        # Select files button
        select_btn = ttk.Button(file_frame, text="Select BibTeX Files", command=self.select_files)
        select_btn.pack(side=tk.LEFT, padx=5)

        # Selected files label
        self.file_label = ttk.Label(file_frame, text="No files selected")
        self.file_label.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        # Analysis button
        analyze_btn = ttk.Button(file_frame, text="Analyze Files", command=self.analyze_files)
        analyze_btn.pack(side=tk.RIGHT, padx=5)

        # Modification frame
        mod_frame = ttk.LabelFrame(main_frame, text="Modify Entry Types", padding="10")
        mod_frame.pack(fill=tk.X, pady=5)

        # Create case conversion options
        self.case_var = tk.StringVar(value="none")
        ttk.Radiobutton(mod_frame, text="No Change",
                        variable=self.case_var, value="none").grid(row=0, column=0, padx=5, sticky=tk.W)
        ttk.Radiobutton(mod_frame, text="First Letter Uppercase (e.g., Article)",
                        variable=self.case_var, value="capitalize").grid(row=0, column=1, padx=5, sticky=tk.W)
        ttk.Radiobutton(mod_frame, text="All Lowercase (e.g., article)",
                        variable=self.case_var, value="lower").grid(row=0, column=2, padx=5, sticky=tk.W)

        # Save options
        self.save_var = tk.StringVar(value="new")
        ttk.Radiobutton(mod_frame, text="Save to new file",
                        variable=self.save_var, value="new").grid(row=1, column=0, padx=5, sticky=tk.W)
        ttk.Radiobutton(mod_frame, text="Overwrite original file",
                        variable=self.save_var, value="original").grid(row=1, column=1, padx=5, sticky=tk.W)

        # Apply changes button
        apply_btn = ttk.Button(mod_frame, text="Apply Changes", command=self.apply_changes)
        apply_btn.grid(row=1, column=2, padx=5, pady=5, sticky=tk.E)

        # Results frame
        results_frame = ttk.LabelFrame(main_frame, text="Analysis Results", padding="10")
        results_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        # Create notebook for tabs
        self.notebook = ttk.Notebook(results_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)

        # Summary tab
        self.summary_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.summary_tab, text="Summary")

        # Create a frame for the chart in the summary tab
        self.chart_frame = ttk.Frame(self.summary_tab)
        self.chart_frame.pack(fill=tk.BOTH, expand=True)

        # Status bar
        self.status_var = tk.StringVar()
        self.status_var.set("Ready")
        status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    def select_files(self):
        """Open file dialog to select BibTeX files"""
        filetypes = [("BibTeX files", "*.bib"), ("All files", "*.*")]
        files = filedialog.askopenfilenames(title="Select BibTeX Files", filetypes=filetypes)

        if files:
            self.selected_files = list(files)
            if len(files) == 1:
                self.file_label.config(text=os.path.basename(files[0]))
            else:
                self.file_label.config(text=f"{len(files)} files selected")
            self.status_var.set(f"Selected {len(files)} file(s)")
        else:
            self.status_var.set("No files selected")

    def parse_bib_file(self, filepath):
        """Parse a BibTeX file and count entry types while storing the content"""
        type_counter = Counter()

        try:
            with open(filepath, 'r', encoding='utf-8') as file:
                content = file.read()
                self.file_contents[filepath] = content

                # Use regex to find all BibTeX entry types (@Article, @Book, etc.)
                # Pattern matches @Type{ where Type is captured
                entry_types = re.findall(r'@(\w+){', content)
                type_counter.update(entry_types)

            return dict(type_counter)
        except Exception as e:
            self.status_var.set(f"Error parsing {os.path.basename(filepath)}: {str(e)}")
            return {}

    def analyze_files(self):
        """Analyze selected BibTeX files"""
        if not self.selected_files:
            self.status_var.set("No files selected for analysis")
            return

        # Clear existing tabs (except Summary)
        for tab in self.notebook.tabs()[1:]:
            self.notebook.forget(tab)

        # Analyze each file and store results
        self.file_analysis = {}
        self.file_contents = {}  # Clear previous file contents
        all_types = set()

        for filepath in self.selected_files:
            filename = os.path.basename(filepath)
            type_counts = self.parse_bib_file(filepath)
            self.file_analysis[filepath] = type_counts
            all_types.update(type_counts.keys())

            # Create a tab for each file
            file_tab = ttk.Frame(self.notebook)
            self.notebook.add(file_tab, text=filename)

            # Create treeview to display types in this file
            tree = ttk.Treeview(file_tab, columns=('count', 'percentage'), show='headings')
            tree.heading('count', text='Count')
            tree.heading('percentage', text='Percentage')
            tree.column('count', width=100, anchor='center')
            tree.column('percentage', width=100, anchor='center')
            tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

            # Add scrollbar
            scrollbar = ttk.Scrollbar(file_tab, orient=tk.VERTICAL, command=tree.yview)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
            tree.configure(yscrollcommand=scrollbar.set)

            # Populate the treeview
            total_entries = sum(type_counts.values())
            for bib_type, count in sorted(type_counts.items()):
                percentage = (count / total_entries * 100) if total_entries > 0 else 0
                tree.insert('', 'end', text=bib_type, values=(count, f"{percentage:.1f}%"))

            # Add chart to each file tab
            if type_counts:
                fig_frame = ttk.Frame(file_tab)
                fig_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

                fig, ax = plt.figure(figsize=(5, 4)), plt.axes()
                types = list(type_counts.keys())
                counts = list(type_counts.values())

                ax.pie(counts, labels=types, autopct='%1.1f%%', startangle=90)
                ax.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle

                canvas = FigureCanvasTkAgg(fig, master=fig_frame)
                canvas.draw()
                canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # Update summary tab
        self.update_summary_view(all_types)

        # Set status
        self.status_var.set(f"Analysis complete: {len(self.selected_files)} file(s) analyzed")

    def update_summary_view(self, all_types):
        print(all_types)
        """Update the summary view with aggregated results"""
        # Clear the chart frame
        for widget in self.chart_frame.winfo_children():
            widget.destroy()

        if not self.file_analysis:
            return

        # Create a combined dataset for all files
        all_files_combined = defaultdict(int)
        for file_data in self.file_analysis.values():
            for bib_type, count in file_data.items():
                all_files_combined[bib_type] += count

        # Sort types by frequency
        sorted_types = sorted(all_files_combined.items(), key=lambda x: x[1], reverse=True)
        types = [item[0] for item in sorted_types]
        counts = [item[1] for item in sorted_types]

        # Create a bar chart for the summary
        fig, ax = plt.subplots(figsize=(8, 4))
        bars = ax.bar(types, counts, color='skyblue')

        # Add count labels on top of bars
        for bar, count in zip(bars, counts):
            height = bar.get_height()
            ax.text(bar.get_x() + bar.get_width() / 2., height,
                    f'{count}', ha='center', va='bottom')

        ax.set_xlabel('BibTeX Entry Types')
        ax.set_ylabel('Count')
        ax.set_title('BibTeX Entry Types Across All Files')

        # Rotate x-axis labels for better visibility if there are many types
        if len(types) > 3:
            plt.xticks(rotation=45, ha='right')

        plt.tight_layout()

        # Embed the chart in the summary tab
        canvas = FigureCanvasTkAgg(fig, master=self.chart_frame)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # Create a frame for a summary table below the chart
        table_frame = ttk.Frame(self.chart_frame)
        table_frame.pack(fill=tk.BOTH, expand=True, pady=10)

        # Add a summary table
        columns = ['Type'] + [os.path.basename(f) for f in self.selected_files] + ['Total']
        tree = ttk.Treeview(table_frame, columns=columns, show='headings')

        # Configure columns
        for col in columns:
            tree.heading(col, text=col)
            tree.column(col, width=100, anchor='center')

        # Add scrollbars
        v_scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=tree.yview)
        v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        h_scrollbar = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=tree.xview)
        h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)

        tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
        tree.pack(fill=tk.BOTH, expand=True)

        # Populate the summary table
        for bib_type in sorted(all_types):
            values = [bib_type]
            total = 0

            for filepath in self.selected_files:
                count = self.file_analysis[filepath].get(bib_type, 0)
                values.append(count)
                total += count

            values.append(total)
            tree.insert('', 'end', values=values)

    def apply_changes(self):
        """Apply the case conversion and save the modified files"""
        if not self.selected_files or not self.file_contents:
            messagebox.showwarning("Warning", "No files to modify. Please analyze files first.")
            return

        case_option = self.case_var.get()
        save_option = self.save_var.get()

        # If no changes are requested, alert the user
        if case_option == "none":
            messagebox.showinfo("Info", "No case conversion selected. No changes will be made.")
            return

        modified_count = 0

        for filepath in self.selected_files:
            content = self.file_contents[filepath]
            filename = os.path.basename(filepath)

            # Apply case conversion to BibTeX entry types
            def case_converter(match):
                entry_type = match.group(1)
                prefix = match.group(0)[0]  # The @ symbol

                if case_option == "capitalize":
                    return f"{prefix}{entry_type[0].upper()}{entry_type[1:].lower()}"
                elif case_option == "lower":
                    return f"{prefix}{entry_type.lower()}"
                else:
                    return match.group(0)

            # Replace entry types in the content
            modified_content = re.sub(r'@(\w+)', case_converter, content)

            # If content was modified
            if modified_content != content:
                modified_count += 1

                # Determine where to save the modified content
                if save_option == "original":
                    output_path = filepath
                else:  # "new"
                    # Ask for save location
                    file_ext = os.path.splitext(filename)[1]
                    suggested_name = f"{os.path.splitext(filename)[0]}_modified{file_ext}"
                    output_path = filedialog.asksaveasfilename(
                        title=f"Save modified version of {filename}",
                        initialfile=suggested_name,
                        defaultextension=file_ext,
                        filetypes=[("BibTeX files", "*.bib"), ("All files", "*.*")]
                    )

                    if not output_path:  # User cancelled
                        continue

                try:
                    with open(output_path, 'w', encoding='utf-8') as file:
                        file.write(modified_content)

                    self.status_var.set(f"Saved modified content to {os.path.basename(output_path)}")
                except Exception as e:
                    messagebox.showerror("Error", f"Failed to save file {os.path.basename(output_path)}: {str(e)}")

        # Show result
        if modified_count > 0:
            messagebox.showinfo("Success", f"Modified and saved {modified_count} file(s).")
        else:
            messagebox.showinfo("Info", "No files were modified.")


if __name__ == "__main__":
    root = tk.Tk()
    app = BibTypeAnalyzer(root)
    root.mainloop()